<?php
namespace Poi\EttsBundle\Model;

/**
 * @author Ricardo J. Vazquez
 * 
 * Abstract class that represents a collection of models from the database. Can only load models. 
 */
abstract class Collection implements \Iterator
{
	protected $conn;
    private $position = 0;
	private $_members = array(); 
	private $_isLoaded = false;
	protected $model = '';
	protected $id = '';
	
	/**
	* Creates the object.
	*
	* @param $conn connection object to the database
	* @param $id optional argument giving an id for the object
	*/
	public function __construct($conn,$id=''){
		$this->conn = $conn;
		$this->id = $id;
		$this->init();
	}
	
	/**
	* Abstract. It is called when the object is created.
	*/
	abstract protected function init();
	
	/**
	* Returns an XML string representation of the object.
	*
	* @param $xml optional. reference to a SimpleXMLElement to which a child element will be added
	*/
	abstract public function toXML(\SimpleXMLElement &$xml=null);
	
	/**
	* Method for adding a model to the collection.
	*
	* @param $obj model to be added to the collection
	* @param $key optional key to assign to the model
	*/
	public function addItem(Model $obj, $key = null) {
		if(!is_a($obj, $this->model)){
			throw new \Exception("$obj not as same type as model");
		}
		
		$this->_checkCallback();
		if($key)  {
			//Throw exception if this key is already in use. 
			if(isset($this->_members[$key]))  {
				throw new KeyInUseException("Key \"$key\" already in use!"); 
			} 
			else  {
				$this->_members[$key] = $obj;
			}
		}
		else { 
			$this->_members[] = $obj;
		}
	}
	
	/**
	* Removes a model from the collection with the given key
	*
	* @param $key key associated to the model
	*/
	public function removeItem($key) {
		$this->_checkCallback();
		if(isset($this->_members[$key])) 
		{
			unset($this->_members[$key]); 
		} 
		else 
		{
			throw new KeyInvalidException("Invalid key \"$key\"!");
		}		
	}
	
	/**
	* Returns a model from the collection with the given key
	*
	* @param $key key associated to the model
	* @return Model with the given key
	*/
	public function getItem($key) {
		$this->_checkCallback();
		if(isset($this->_members[$key])) 
		{
			return $this->_members[$key]; 
		} 
		else 
		{
			throw new KeyInvalidException("Invalid key \"$key\"!");
		}
	}

	/**
	* Returns all the keys of the collection
	*
	* @return all the keys of the collection
	*/
	public function keys() {
		$this->_checkCallback();
		return array_keys($this->_members);
	}
	
	/**
	* Returns length of the collection
	*
	* @return int length of the collection
	*/
	public function length() {
		$this->_checkCallback();
		return sizeof($this->_members);
	}
	
	/**
	* Returns true/false if the model exists
	*
	* @param $key key associated to the model
	* @return boolean 
	*/
	public function exists($key) {
		$this->_checkCallback();
		return (isset($this->_members[$key]));
	}
	
	/** 
	 * Use this method to define a function to be 
	 * invoked prior to accessing the collection. 
	 * The function should take a collection as a 
	 * its sole parameter. 
 	 * @param $functionName the function to call
 	 * @param $model the type of model it is
	 * @param $objOrClass what object contains the function
	 */ 
	public function setLoadCallback($functionName, $model, $objOrClass = null) {
		//Maybe I dont have to do this
		$model = "Poi\\EttsBundle\\Model\\$model";
		
		$rc = new \ReflectionClass( $model );
		
		if(!$rc->isSubclassOf("Poi\\EttsBundle\\Model\\Model")){
			throw new \Exception("$model is not a Model");
		}
		
		$this->model = $model;
		
		if($objOrClass) 
		{ 
			$callback = array($objOrClass, $functionName);
		}
		else 
		{ 
			$callback = $functionName;
		}
		
		if(!is_callable($callback, false, $callableName)) 
		{
			throw new \Exception("$callableName is not callable as a parameter to onload");
			return false;
		}
		
		$this->_onload = $callback;
		
	}
	
	/** 
	 * Check to see if a callback has been defined and if so, 
	 * whether or not it has already been called. If not, 
	 * invoke the callback function. 
	 */ 
	private function _checkCallback() {
		if(isset($this->_onload) && !$this->_isLoaded) 
		{
			$this->_isLoaded = true; 
			call_user_func($this->_onload, $this);
		}
	}
	
	//Iterator interface
	public function rewind() {
		$this->_checkCallback();
		$this->position = 0;
	}
	
	public function current() {
		$this->_checkCallback();
		return $this->getItem($this->position);
	}
	
	public function key() {
		$this->_checkCallback();
		return $this->position;
	}
	
	public function next() {
		$this->_checkCallback();
		++$this->position;
	}
	
	public function valid() {
		$this->_checkCallback();
		return $this->exists($this->position);
	}
	


}